package im.composer.audio.engine;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.stream.Collectors;

import org.jaudiolibs.audioservers.AudioConfiguration;
import org.tritonus.share.sampled.FloatSampleBuffer;

import jass.engine.SinkIsFullException;

/**
 * Input/output unit. Needs only implementation of computeBuffer(). Each
 * attached source can be labeled passive, which means that we will call
 * getBuffer() without a time stamp on it, so no computation is triggered. This
 * is needed only when using the ThreadMixer which would result in deadlocks on
 * closed loops. So you have to explicitly mark source connections passive to
 * eliminate loops. When using only 1 thread this is not needed.
 * 
 * @author Kees van den Doel (kvdoel@cs.ubc.ca)
 */

public abstract class InOut extends Out implements Sink {

	private Vector<Source> sourceContainer = new Vector<Source>();

	/**
	 * add source to Sink.
	 * 
	 * @param s
	 *            Source to add.
	 * @return object representing Source in Sink (may be null).
	 */
	public Object addSource(Source s) throws SinkIsFullException {
		return addSource(s, false);
	}

	/**
	 * add source to Sink, can flag as passive (so will trigger no computation)
	 * 
	 * @param s
	 *            Source to add.
	 * @param passive
	 *            flag; true if passive, otherwise will be active (normal)
	 * @return object representing Source in Sink (may be null).
	 */
	public synchronized Object addSource(Source s, boolean p) throws SinkIsFullException {
		if (s == null) {
			throw new IllegalArgumentException();
		}
		sourceContainer.addElement(s);
		s.setTime(getTime());
		if (s instanceof InOut) {
			try {
				((InOut) s).configure(getContext());
			} catch (Exception e) {
			}
		}
		return null;
	}

	public synchronized void removeSource(Source s) {
		int i = sourceContainer.indexOf(s);
		if (i < 0) {
			return;
		}
		removeSource(i);
	}

	@Override
	public void removeSource(int i) {
		if (i < 0) {
			return;
		}
		sourceContainer.removeElementAt(i);
	}

	public List<Source> getSources() {
		return Collections.unmodifiableList(sourceContainer);
	}

	/**
	 * Array of buffers of the sources
	 */
	protected transient Set<FloatSampleBuffer> srcBuffers;

	public InOut(AudioConfiguration context) {
		super(context);
	}

	@Override
	public synchronized void configure(AudioConfiguration context) throws Exception {
		super.configure(context);
		srcBuffers = new HashSet<>();
		sourceContainer.parallelStream().forEach(src->{try {
			src.configure(context);
		} catch (Exception e) {
		}});
	}

	/**
	 * Call all the sources and cache their returned buffers.
	 */
	protected final void callSources() {
		srcBuffers = sourceContainer.parallelStream().map(src -> {
			try {
				return src.getBuffer(getTime());
			} catch (Exception e) {
				return new FloatSampleBuffer(getContext().getOutputChannelCount(), getContext().getMaxBufferSize(),
						getContext().getSampleRate());
			}
		}).collect(Collectors.toCollection(() -> Collections.synchronizedSet(new HashSet<>())));

	}

	@Override
	protected void prepareBuffer() {
		callSources();
		super.prepareBuffer();
	}

	/**
	 * Reset time of self and all inputs
	 * 
	 * @param t
	 *            time to reset to. Patch must be in a state s.t. none of the
	 *            current times == t
	 */
	public synchronized void resetTime(long t) {
		setTime(t);
		resetTimeSources(t);
	}

	/**
	 * Call all the sources and reset time
	 */
	private final void resetTimeSources(long t) {
		sourceContainer.parallelStream().filter(src -> src instanceof Out).map(src -> ((Out) src))
				.filter(src -> src.getTime() != t).forEach(src -> src.resetTime(t));
	}

}
